﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Engine
{
    /// <summary>
    /// Simple no-return-value, no-parameters delegate type
    /// </summary>
    public delegate void VoidDelegate();

    /// <summary>
    /// A Port attached to a Component becomes a point for sending data to, or receiving data from,
    /// Connection objects.  Note that it is abstract -- while technically, all Port objects can
    /// have either an incoming or outgoing Connection (or both!), we instantiate them as either
    /// InPort or OutPort objects for the sake of clarifying their purpose within code.
    /// <para>
    /// The reason for having both InPorts and OutPorts be effectively the same object is that in
    /// certain situations (specifically, in CompositeComponents) an InPort may send as well as
    /// receive (and, similarly, an OutPort will receive as well as send).
    /// </para>
    /// </summary>
    /// <typeparam name="T">the type of data held by the Port</typeparam>
    public abstract class Port<T>
    {
		private static int nextPortID = 1;

        // the incoming Connection object, if any
        private Connection<T> incomingConnection;

        // the outgoing Connection object, if any
        private Connection<T> outgoingConnection;

        // the Port's internal storage buffer
		private T myValue;

        /// <summary>
        /// The value received by, or sent from, the Port
        /// </summary>
		public virtual T Value 
		{
			get { return this.myValue; }
			set
			{
				this.myValue = value;
				if (this.outgoingConnection != null)
				{
					this.outgoingConnection.Send(this.myValue);
				}
			}
		}

        /// <summary>
        /// Creates a new Port
        /// </summary>
        public Port()
        {
            this.incomingConnection = null;
            this.outgoingConnection = null;
            this.myValue = default(T);
        }

        /// <summary>
        /// Attaches an incoming Connection to this Port
        /// </summary>
        /// <param name="incoming">the Connection to attach</param>
        public void ConnectIncoming(Connection<T> incoming)
        {
            this.incomingConnection = incoming;
        }

        /// <summary>
        /// Disconnects any incoming Connection from this Port
        /// </summary>
        public void DisconnectIncoming()
        {
            this.incomingConnection = null;
        }

        /// <summary>
        /// Attaches an outgoing Connection to this Port
        /// </summary>
        /// <param name="outgoing">the Connection to attach</param>
        public void ConnectOutgoing(Connection<T> outgoing)
        {
            this.outgoingConnection = outgoing;
        }

        /// <summary>
        /// Disconnects any outgoing Connection from this Port
        /// </summary>
        public void DisconnectOutgoing()
        {
            this.outgoingConnection = null;
        }

		/// <summary>
		/// Generates a unique port ID
		/// </summary>
		protected int GetNextPortID()
		{
			int id = nextPortID;
			nextPortID++;
			return id;
		}
    }

	/// <summary>
	/// Exposes some useful port information in a non-generic way (e.g., in case it needs to 
	/// be collected into an array).
	/// </summary>
	public interface IPortInfo
	{
		/// <summary>
		/// unique identifier for this port; usually set to the name of the variable
		/// associated with the port
		/// </summary>
		string Name { get; set; }

		/// <summary>
		/// 
		/// </summary>
		/// <returns>Returns the type defined on the generic instance of InPort<T></returns>
		Type GetGenericType();
	}

    /// <summary>
    /// This is a hack to provide non-generic reflection access to the Run property of all InPort objects.
    /// Doing this because trying to access a generic object via reflection is a pain in the posterior.
    /// </summary>
    public interface IInPort: IPortInfo
    {
        /// <summary>
        /// Delegate executed when this InPort's Value property is set.
        /// </summary>
        VoidDelegate Run { set; }
    }

    /// <summary>
    /// A Port intended for receiving data from a Connection.
    /// </summary>
    /// <typeparam name="T">the type of data received by this InPort</typeparam>
    public class InPort<T> : Port<T>, IInPort
    {
        /// <summary>
        /// The delegate executed by this InPort when its Value property is set.
        /// </summary>
        public VoidDelegate Run { private get; set; }

        /// <summary>
        /// Default constructor, providing an empty Run() method
        /// </summary>
        public InPort() : base() 
        {
            this.Run = new VoidDelegate(delegate() { });
			this.Name = "iport" + base.GetNextPortID().ToString();
        }

        /// <summary>
        /// Constructor to specify the InPort's Run property
        /// </summary>
        /// <param name="runMethod">a delegate that is to be executed when this InPort's Value property is set</param>
        public InPort(VoidDelegate runMethod)
            : base()
        {
            this.Run = runMethod;
        }

        public override T Value
        {
            set
            {
                base.Value = value;
                this.Run();
            }
        }

		public Type GetGenericType()
		{
			return this.GetType().GetGenericArguments()[0];
		}

		public string Name { get; set; }
    }

	/// <summary>
	/// Generics-free view of an OutPort
	/// </summary>
	public interface IOutPort: IPortInfo
	{
	}

    /// <summary>
    /// A Port intended for sending data to a Connection
    /// </summary>
    /// <typeparam name="T">the type of data sent by this OutPort</typeparam>
    public class OutPort<T> : Port<T>, IOutPort 
	{
		public OutPort()
		{
			this.Name = "oport" + base.GetNextPortID().ToString();
		}

		public Type GetGenericType()
		{
			return this.GetType().GetGenericArguments()[0];
		}

		public string Name { get; set; }
	}
}